package com.seashell.appstartupjob.expression;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

import com.seashell.appstartupjob.exceptions.ExpressionWrongException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * com.seashell.appstartupjob.expression.PathExpression
 * 路径表达式
 *
 * @author Allen
 * @version v1.0
 * @date 2017/7/18
 */
public class PathExpression {

    private static final Logger logger = LoggerFactory.getLogger(PathExpression.class);

    /**
     * 任意字符串不包含斜杠'/'
     */
    private static String ANY_WORD_EXPT_SPLITER = "((?!/).)*";

    /**
     * 文件与文件夹匹配
     */
    private static Pattern FILES_AND_DIRS = Pattern.compile(".*\\*\\*.*");

    /**
     * 文件匹配
     */
    private static Pattern FILES = Pattern.compile(".*\\*.*");

    private String expression;

    private Pattern expressionPattern;
    private List<Pattern> includesPattern;
    private List<Pattern> excludesPattern;

    public PathExpression(String expression) {
        this.expression = expression;
        expressionPattern = resolveExpression(this.expression);

        if(logger.isDebugEnabled()){
            logger.debug("Expression regex:[{}]",this.expressionPattern.toString());
        }
    }

    public PathExpression(String expression, Set<String> includes, Set<String> excludes) {
        this(expression);

        includesPattern = resolveExpressionCollection(includes);
        excludesPattern = resolveExpressionCollection(excludes);
    }

    /**
     * 路径是否符合表达式
     * 包含判断includes和excludes的设置
     * @param path
     * @return
     */
    public boolean isInclude(String path){

        Matcher matcher = expressionPattern.matcher(path);
        boolean isContainInExpression = matcher.matches();

        Boolean isContainInIncludes = includesFilter(path);
        Boolean isContainInExcludes = excludesFilter(path);

        /**
         * 如果该文件同时被包含和不包含选中，则配置错误
         */
        if(isContainInIncludes != null && isContainInExcludes != null
                && isContainInIncludes && isContainInExcludes){
            throw new ExpressionWrongException("path:"+path+"同时包含于includes和excludes中");
        }

        if(isContainInExpression && (isContainInIncludes==null?true:isContainInIncludes)
                && (isContainInExcludes==null?false:!isContainInExcludes)){
            logger.debug("path include:[{}]",path);
            return true;
        }
        logger.debug("path exclude:[{}]",path);
        return false;
    }

    /**
     * 将表达式结合解析为正则
     * @param expressions
     * @return
     */
    public List<Pattern> resolveExpressionCollection(Set<String> expressions){
        if(expressions != null && expressions.size() > 0){
            List<Pattern> targetList = new ArrayList<>();
            for (String s : expressions) {
                targetList.add(resolveExpression(s));
            }
            return targetList;
        }
        return null;
    }

    /**
     * 是否符合includes条件
     * @param path
     * @return
     */
    public Boolean includesFilter(String path){
        if(includesPattern == null || includesPattern.size()==0){
            return null;
        }
        boolean isInclude = false;
        for (Pattern pattern : includesPattern) {
            Matcher matcher = pattern.matcher(path);
            if(matcher.matches()){
                isInclude = true;
            }
        }
        return isInclude;
    }

    /**
     * 是否符合excludes条件
     * @param path
     * @return
     */
    public Boolean excludesFilter(String path){
        if(excludesPattern == null || excludesPattern.size()==0){
            return null;
        }
        boolean isExclude = false;
        for (Pattern pattern : excludesPattern) {
            Matcher matcher = pattern.matcher(path);
            if(matcher.matches()){
                isExclude = true;
            }
        }
        return isExclude;
    }

    /**
     * 解析表达式
     * @param expression
     */
    private Pattern resolveExpression(String expression){
        if(StringUtils.isEmpty(expression)){
            return null;
        }
        expression = expression.replaceAll("\\\\", "/").trim();
        expression = expression.replaceAll("//","/");
        String[] expressionPiece = expression.split("/");
        if(expressionPiece.length < 2){
            throw new ExpressionWrongException("错误的表达式格式");
        }

        StringBuilder expressionRegexStr = new StringBuilder("^");

        for (int i = 0 ; i < expressionPiece.length ; i++) {
            String piece = expressionPiece[i];
            if(StringUtils.isEmpty(piece)){
                continue;
            }
            if(i!=0){
                expressionRegexStr.append("/{1}");
            }

            //是否匹配文件与文件夹
            Matcher matcher = FILES_AND_DIRS.matcher(piece);
            Matcher fileMatcher = FILES.matcher(piece);
            if(matcher.matches()){
                if(!piece.equals("**")){
                    throw new ExpressionWrongException("**不能和其他字符同时出啊现在一个区域");
                }
                expressionRegexStr.append(".*");
                if(i != expressionPiece.length-1){
                    expressionRegexStr.append("?");
                }
            }
            //是否匹配文件
            else if(fileMatcher.matches()){
                //匹配所有文件
                if(piece.equals("*")){
                    //只能出现在最后一个
                    if(i != expressionPiece.length-1){
                        throw new ExpressionWrongException("单*符号匹配只能出现在最后一个节");
                    }
                    expressionRegexStr.append(ANY_WORD_EXPT_SPLITER);
                //匹配带表达式的文件
                }else{
                    String[] pieceArray = piece.split("\\*");
                    StringBuilder subExpression = new StringBuilder();

                    for (int i1 = 0; i1 < pieceArray.length; i1++) {
                        String s = pieceArray[i1];
                        subExpression.append(regexEscape(s));
                        if(i1 != pieceArray.length-1){
                            subExpression.append(ANY_WORD_EXPT_SPLITER);
                        }
                    }
                    if(piece.endsWith("*")){
                        subExpression.append(ANY_WORD_EXPT_SPLITER);
                    }
                    expressionRegexStr.append(subExpression);
                }
            //普通字符串
            }else{
                expressionRegexStr.append(regexEscape(piece));
            }
        }

        return Pattern.compile(expressionRegexStr.toString());
    }

    /**
     * 正则保留字符逃逸
     * @param expression
     * @return
     */
    private String regexEscape(String expression){
        expression = expression.replaceAll("\\^","\\\\\\^");
        expression = expression.replaceAll("\\$","\\\\\\$");
        expression = expression.replaceAll("\\(","\\\\\\(");
        expression = expression.replaceAll("\\)","\\\\\\)");
        expression = expression.replaceAll("\\[","\\\\\\[");
        expression = expression.replaceAll("\\{","\\\\\\{");
        expression = expression.replaceAll("\\|","\\\\\\|");
        expression = expression.replaceAll("\\.","\\\\\\.");
        expression = expression.replaceAll("\\*","\\\\\\*");
        expression = expression.replaceAll("\\+","\\\\\\+");
        expression = expression.replaceAll("\\?","\\\\\\?");
        return expression;
    }
}
